package com.h3bpm.starcharge.service.impl.alm;

import OThinker.Common.Organization.Models.User;
import OThinker.H3.Entity.Data.Attachment.Attachment;
import OThinker.H3.Entity.DataModel.BizObject;
import OThinker.H3.Entity.IEngine;
import OThinker.H3.Entity.Instance.Data.InstanceData;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.h3bpm.base.util.AppUtility;
import com.h3bpm.base.util.CharsetUtil;
import com.h3bpm.starcharge.bean.alm.AlmCreateWorkflow;
import com.h3bpm.starcharge.common.uitl.FileUploadUtil;
import com.h3bpm.starcharge.common.uitl.HttpClientUtil;
import com.h3bpm.starcharge.param.workflow.StartWorkflowNewParam;
import com.h3bpm.starcharge.ret.workflow.StartWorkflowNewRet;
import com.h3bpm.starcharge.service.alm.AlmWorkflowService;
import com.h3bpm.starcharge.service.bpm.StarChargeBpmService;
import com.h3bpm.starcharge.support.core.H3FieldEntity;
import com.h3bpm.starcharge.support.utils.H3FieldUtils;
import data.DataException;
import data.DataRow;
import data.DataTable;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.*;

/***
 * create by lvyz on 2019/5/24
 */
@Service
public class AlmWorkflowServiceImpl implements AlmWorkflowService {

	private static final Logger log = LoggerFactory.getLogger(AlmWorkflowServiceImpl.class);

	// 业务编码key
	private static final String SERVICE_ID_KEY = "keyId";

	// BPM实例标识key
	private static final String WORK_FLOW_INSTANCE_KEY = "instanceId";

	// 附件字段名
	private static final String DATA_FIELD = "attachments";

	@Value("${support.host}")
	private String ALM_HOST;

	@Value("${support.account}")
	private String USER_NAME;

	@Value("${support.password}")
	private String PASSWORD;

	@Autowired
	private StarChargeBpmService starChargeBpmService;

	@Override
	@Transactional
	public List<JSONObject> createFlows(AlmCreateWorkflow flowEntity) {

		log.info("[ALM] 创建审批流程 流程编码 = {}", flowEntity.getWorkflowCode());

		// 成功创建的流程实例ID集合
		List<String> successInstanceId = new ArrayList<>();

		// 返回对象
		List<JSONObject> list = new ArrayList<>();

		Long currentServiceCode = 0L;

		try {

			// 流程参数
			JSONArray array = JSONObject.parseArray(flowEntity.getFormParam());

			for (Object obj : array) {

				// 转换JSON对象
				JSONObject formObj = JSONObject.parseObject(obj.toString());

				// 业务编号
				Long serviceCode = formObj.getLong(SERVICE_ID_KEY);
				if(null == serviceCode){
					throw new RuntimeException("参数 keyId 不可以为空");
				}

				currentServiceCode = serviceCode;
				// 流程模板编号
				String workflowCode = flowEntity.getWorkflowCode();
				// 账户标识
				String accountCode = flowEntity.getAccountMobile();
				// 是否跳过第一个流程
				String finishStart = flowEntity.isFinishStart();

				JSONObject jsonObject = this.createFlow(workflowCode, accountCode, finishStart, serviceCode, formObj);

				list.add(jsonObject);

				successInstanceId.add(jsonObject.getString("instanceId"));
			}

		} catch (Exception e) {

			log.error("[ALM] 创建流程失败, 删除本次已经创建的流程实例");
			for (String instanceId : successInstanceId) {
				// 删除流程实例
				this.deleteInstance(instanceId);
			}

			throw new RuntimeException(e.getMessage() + " 业务编号 = " + currentServiceCode);
		}

		return list;
	}

	@Override
	@Transactional
	public void success(String instanceId, String workflowCode) {

		log.info("审批通过 instanceId = {}, workflowCode = {}", instanceId, workflowCode);

		JSONObject jsonObject = this.successParams(instanceId, workflowCode);

		this.callBackAlm(jsonObject);
	}

	@Override
	@Transactional
	public void contractsSuccess(String instanceId, String workflowCode) throws Exception {

		log.info("合同审批通过 instanceId = {}, workflowCode = {}", instanceId, workflowCode);

		JSONObject jsonObject = this.contractsSuccessParams(instanceId, workflowCode);

		this.callBackAlm(jsonObject);
	}


	@Override
	@Transactional
	public void refuse(String instanceId, String workflowCode) {

		log.info("审批驳回 instanceId = {}, workflowCode = {}", instanceId, workflowCode);

		this.deleteInstance(instanceId);

		JSONObject jsonObject = this.successParams(instanceId, workflowCode);

		this.callBackAlm(jsonObject);
	}


	/**
	 * 创建流程实体
	 *
	 * @param workflowCode
	 * @param accountCode
	 * @param finishStart
	 * @param serviceCode
	 * @param formObj
	 * @return
	 */
	private JSONObject createFlow(String workflowCode, String accountCode, String finishStart, Long serviceCode, JSONObject formObj) {

		// 唯一性校验
		// this.verifyOnly(serviceCode, workflowCode);

		if (null != formObj.getString("salesPhone")) {
			// 获取销售员的Id
			formObj.put("salesId", this.findUserId(formObj.getString("salesPhone")));
		}

		StartWorkflowNewParam workflowParam = new StartWorkflowNewParam(workflowCode, accountCode, finishStart, formObj.toJSONString());

		log.info("[ALM] 创建流程实例 | {}", JSONObject.toJSONString(workflowParam));
		// 执行创建流程实例方法
		StartWorkflowNewRet startWorkflowRet = this.starChargeBpmService.startWorkflowNew(workflowParam);

		// 实例创建成功-上传附件
		String fileUrls = formObj.getString(AlmCreateWorkflow.FILE_URL);
		if (startWorkflowRet.isSuccess() && fileUrls != null) {
			String instanceId = startWorkflowRet.getInstanceID();
			this.uploadFileBatch(fileUrls, workflowCode, instanceId);
		}

		if (!startWorkflowRet.isSuccess()) {
			log.error("[ALM] 创建流程实例失败 MSG = {}, workflowCode = {}", startWorkflowRet.getMessage(), workflowParam.getWorkflowCode());
			throw new RuntimeException("创建流程实例失败");
		}

		// 创建业务与工作流程关联关系
		this.createRelation(startWorkflowRet.getInstanceID(), serviceCode, workflowCode.trim());

		JSONObject resultObj = new JSONObject();
		resultObj.put(SERVICE_ID_KEY, serviceCode);
		resultObj.put(WORK_FLOW_INSTANCE_KEY, startWorkflowRet.getInstanceID());

		return resultObj;
	}

	/**
	 * 批量上传附件
	 *
	 * @param httpUrls
	 * @param workflowCode
	 * @param instanceId
	 */
	private void uploadFileBatch(String httpUrls, String workflowCode, String instanceId) {

		if (StringUtils.isEmpty(httpUrls)) {
			return;
		}

		// 拆分符号替换
		httpUrls = httpUrls.replace("，", ",");

		// 根据,拆分
		String[] urlArr = httpUrls.split(",");

		// 循环上传附件
		for (String fileUrl : urlArr) {
			// 上传文件
			this.uploadFile(fileUrl, workflowCode, instanceId);
		}
	}

	/**
	 * 上传附件
	 *
	 * @param httpUrl
	 * @param workflowCode
	 * @param instanceId
	 */
	private void uploadFile(String httpUrl, String workflowCode, String instanceId) {

		try {

			// 获取文件大小
			int allowedSize = FileUploadUtil.getAllowedMaxSize(false);

			URL url = new URL(httpUrl);

			HttpURLConnection conn = (HttpURLConnection) url.openConnection();

			if (conn == null) {
				throw new RuntimeException("未获取到文件流");
			}

			if (conn.getContentLength() > allowedSize) {
				throw new RuntimeException("附件超过最大限制");
			}

			InputStream in = conn.getInputStream();
			// 输入流转换内容字节码
			ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
			byte[] bytes = new byte[1024];
			int i;
			while ((i = in.read(bytes, 0, 1024)) > 0) {
				byteOut.write(bytes, 0, i);
			}
			byte[] contents = byteOut.toByteArray();
			String charset = CharsetUtil.getCharset(in);
			if (httpUrl.endsWith(".txt")) {
				contents = (new String(contents, charset)).getBytes("utf-8");
			}

			// 获取文件名称
			String fileName = httpUrl.substring(httpUrl.lastIndexOf("//"), httpUrl.length());

			// 初始化需要上传的附件
			Attachment attachment = new Attachment();
			attachment.setObjectID(UUID.randomUUID().toString());
			attachment.setContent(contents);
			attachment.setContentType(conn.getHeaderField("Content-Type"));
			attachment.setCreatedBy(User.AdministratorID);
			attachment.setCreatedTime(new Date());
			attachment.setFileName(fileName);
			attachment.setLastVersion(true);
			attachment.setModifiedTime(new Date());
			// 流程编码
			attachment.setBizObjectSchemaCode(workflowCode);
			attachment.setContentLength(conn.getContentLength());
			// 获取附件与流程实例的关联ID--不知道是什么ID关联的
			InstanceData instanceData = new InstanceData(AppUtility.getEngine(), instanceId, User.AdministratorID);
			attachment.setBizObjectId(instanceData.getBizObject().getObjectID());

			attachment.setDataField(DATA_FIELD);
			AppUtility.getEngine().getBizObjectManager().AddAttachment(attachment);

		} catch (Exception e) {

			log.error("[ALM] 上传审批附件失败，MSG = {} | instanceId = {}, workflowCode = {} , fileUrl = {}", new Object[]{e.getMessage(), instanceId, workflowCode, httpUrl});
			throw new RuntimeException("上传审批附件失败 " + e.getMessage());
		}
	}

	/**
	 * 删除流程实例
	 *
	 * @param instanceId
	 */
	private void deleteInstance(String instanceId) {

		try {

			// 删除流程实例
			AppUtility.getEngine().getInstanceManager().RemoveInstance(instanceId, true);

			// 删除绑定关系
			this.deleteRelation(instanceId);

		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * 删除关联关系
	 *
	 * @param instanceId
	 */
	private void deleteRelation(String instanceId) {

		String sql = "DELETE FROM alm_workflow WHERE instance_id = '%s' ";

		sql = String.format(sql, instanceId);

		AppUtility.getEngine().getEngineConfig().getCommandFactory().CreateCommand().ExecuteNonQuery(sql);
	}

	/**
	 * 创建关联关系
	 *
	 * @param instanceId
	 * @param serviceId
	 */
	private void createRelation(String instanceId, Long serviceId, String workflowCode) {

		// 修改bpm数据库
		String sql = "INSERT INTO alm_workflow (instance_id,service_id, workflow_code, create_time) VALUES ('%s', %s, '%s',GETDATE())";

		sql = String.format(sql, instanceId, serviceId, workflowCode);

		log.info(sql);

		AppUtility.getEngine().getEngineConfig().getCommandFactory().CreateCommand().ExecuteNonQuery(sql);
	}

	/**
	 * 校验唯一性
	 *
	 * @param serviceId
	 * @param workflowCode
	 */
	private void verifyOnly(Long serviceId, String workflowCode) {

		String sql = "SELECT Count(1) FROM alm_workflow WHERE service_id = '%s' AND workflow_code = '%s'";
		sql = String.format(sql, serviceId, workflowCode);

		// 查询数据库
		DataTable dataTable = AppUtility.getEngine().getQuery().QueryTable(sql);

		// 获取数据
		if (null != dataTable) {
			if (dataTable.getRows().size() > 0) {
				DataRow dataRow = dataTable.getRows().get(0);
				Integer count;
				try {
					count = dataRow.getInt(0);
				} catch (DataException e) {
					e.printStackTrace();
					throw new RuntimeException("业务编号 = " + serviceId + " 流程编码 = " + workflowCode + " 解析查询数据异常 MSG = " + e.getMessage());
				}

				if (count > 0) {
					log.error("[ALM] 校验唯一性流程实例已经存在 serviceId = {}, workflowCode = {} ", serviceId, workflowCode);
					throw new RuntimeException("业务编号 = " + serviceId + " 流程编码 = " + workflowCode + " 已经创建了流程实例");
				}
			}
		}
	}

	/**
	 * 审批完成-调用ALM回调
	 *
	 * @param jsonObject
	 * @return
	 */
	private void callBackAlm(JSONObject jsonObject) {

		log.info("审批完成 调用ALM | {}", jsonObject.toJSONString());

		// alm 鉴权认证 获取请求令牌
		String accessToken = this.almOauth();

		// 设置请求头部信息
		Map<String, String> header = new HashMap<>();
		header.put("Authorization", "Bearer" + " " + accessToken);
		header.put("Content-Type", "application/json");

		// 创建请求参数
		String outputStr = jsonObject.toJSONString();

		// 发送请求
		try {
			JSONObject resp = HttpClientUtil.post(this.ALM_HOST, "/amdm/v1/0/apply/response", header, outputStr);

			if (!"S".equals(resp.getString("status"))) {
				log.warn("[ALM] 回调接口异常 {}", resp.getString("msg"));
			}

		} catch (Exception e) {
			log.error("[ALM] 回调接口异常 {}", e.getMessage());
			e.printStackTrace();
		}
	}

	/**
	 * ALM鉴权认证
	 *
	 * @return
	 */
	private String almOauth() {

		String url = new StringBuilder("http://10.9.46.125:8020").append("/oauth/token?").append("username=").append(this.USER_NAME).append("&password")
				.append(this.PASSWORD).toString();

		// 获取token
		String clientRet = HttpClientUtil.doGet(url);

		JSONObject jsonObj = JSONObject.parseObject(clientRet);

		return jsonObj.getString("access_token");
	}

	/**
	 * 审批失败参数
	 *
	 * @param instanceId
	 * @param workflowCode
	 * @return
	 */
	private JSONObject refuseParams(String instanceId, String workflowCode) {

		JSONObject jsonObject = new JSONObject();

		// 查询驳回原因
		String comment = this.findRefuseReason(instanceId);

		jsonObject.put("message", comment);
		jsonObject.put("status", "E");

		String serviceId = this.findServiceId(instanceId);

		jsonObject.put("workflowCode", workflowCode);
		jsonObject.put("workflowKey", serviceId);

		return jsonObject;
	}

	/**
	 * 审批通过参数
	 *
	 * @param instanceId
	 * @param workflowCode
	 * @return
	 */
	private JSONObject successParams(String instanceId, String workflowCode) {

		JSONObject jsonObject = new JSONObject();

		jsonObject.put("message", "审批通过");
		jsonObject.put("status", "S");

		String serviceId = this.findServiceId(instanceId);

		jsonObject.put("workflowCode", workflowCode);
		jsonObject.put("workflowKey", serviceId);

		return jsonObject;
	}

	/**
	 * 审批通过参数
	 *
	 * @param instanceId
	 * @param workflowCode
	 * @return
	 */
	private JSONObject contractsSuccessParams(String instanceId, String workflowCode) throws Exception {

		IEngine client = AppUtility.getEngine();

		// 获取表单审批内容
		Map<String, Object> valueTable = new InstanceData(client, instanceId, User.AdministratorID).getBizObject().getValueTable();

		// 获取表单属性名称
		List<H3FieldEntity> h3FieldEntityList = H3FieldUtils.getPropertySchemaList(workflowCode);

		// 完整的表单数据对象
		JSONObject jsonObject = new JSONObject();

		// 循环全部的属性名称
		for (H3FieldEntity entity : h3FieldEntityList) {

			if(!entity.hasChild()){

				// 主表数据拼装
				jsonObject.put(entity.getName(), valueTable.get(entity.getName()));
			}

			if(entity.hasChild()){

				// 获取子表名称
				String key = entity.getName();

				// 子表数据JSON
				JSONArray table = new JSONArray();

				// 获取子表数据遍历
				for (BizObject bizObject : (BizObject[]) valueTable.get(key)) {

					// 获取的子表数据存放Map
					Map<String, Object> childTable = bizObject.getValueTable();

					// 每行为一个JSON对象
					JSONObject row  = new JSONObject();

					// 获取子表头名称
					for (H3FieldEntity childEntity : entity.getChildList()) {

						// 设置子表行数据 key->value 组成一个单元格
						row.put(childEntity.getName(), childTable.get(childEntity.getName()));
					}

					// 设置子表行数据到表格数据中
					table.add(row);
				}

				// 设置表格数据到完整的表单数据中-子表名称去掉流程编码
				jsonObject.put(key.replace(workflowCode,""), table);
			}
		}

		JSONObject result = new JSONObject();
		result.put("message", "审批通过");
		result.put("status", "S");

		String serviceId = this.findServiceId(instanceId);

		result.put("workflowCode", workflowCode);
		result.put("workflowKey", serviceId);
		result.put("data", jsonObject);

		return result;
	}

	/**
	 * 查询驳回意见
	 *
	 * @param instanceId
	 * @return
	 */
	private String findRefuseReason(String instanceId) {

		String sql = "SELECT top 1 content FROM SC_workflowcomment WHERE instanceId = '%s' ORDER by gmtCreate desc";

		sql = String.format(sql, instanceId);

		DataTable dataTable = AppUtility.getEngine().getQuery().QueryTable(sql);

		if (null != dataTable) {
			if (dataTable.getRows().size() > 0) {
				DataRow dataRow = dataTable.getRows().get(0);
				try {
					return dataRow.getString(0);
				} catch (DataException e) {
					log.error("查询驳回意见 数据异常 {}", e.getMessage());
				}
			}
		}

		return null;
	}

	/**
	 * 查询用户ID
	 *
	 * @param code
	 * @return
	 */
	private String findUserId(String code) {

		String sql = "SELECT ObjectID from OT_User WHERE Code = '%s'";

		sql = String.format(sql, code);

		DataTable dataTable = AppUtility.getEngine().getQuery().QueryTable(sql);

		if (null != dataTable) {
			if (dataTable.getRows().size() > 0) {
				DataRow dataRow = dataTable.getRows().get(0);
				try {
					return dataRow.getString(0);
				} catch (DataException e) {
					log.error("查询用户ID 数据异常 {}", e.getMessage());
					e.printStackTrace();
				}
			}
		}

		log.error("code = {} 用户不存在", code);
		throw new RuntimeException("code = " + code + " 用户不存在");
	}

	/**
	 * 通过流程实例ID查询业务ID
	 *
	 * @param instanceId
	 * @return
	 */
	private String findServiceId(String instanceId) {

		String sql = "SELECT service_id FROM alm_workflow WHERE instance_id = '%s'";

		sql = String.format(sql, instanceId);

		DataTable dataTable = AppUtility.getEngine().getQuery().QueryTable(sql);

		if (null != dataTable) {
			if (dataTable.getRows().size() > 0) {
				DataRow dataRow = dataTable.getRows().get(0);
				try {
					return dataRow.getString(0);
				} catch (DataException e) {
					log.error("查询业务ID 数据异常 {}", e.getMessage());
					e.printStackTrace();
				}
			}
		}

		log.error("instanceId = {} 业务ID不存在", instanceId);
		throw new RuntimeException("instanceId = " + instanceId + " 业务ID不存在");

	}
}
